#!/usr/bin/env python
# We're using optparse because we need to support 2.6
# which doesn't have argparse.  Given that argparse is
# a dependency that eventually gets installed, we could
# try to bootstrap, but using optparse is just easier.
import optparse
import os
import platform
import shutil
import subprocess
import sys
import tarfile
import tempfile

from contextlib import contextmanager


PACKAGES_DIR = os.path.join(
    os.path.dirname(os.path.abspath(__file__)), 'packages')
INSTALL_DIR = os.path.expanduser(os.path.join(
    '~', '.local', 'lib', 'aws'))


class BadRCError(Exception):
    pass


class MultipleBundlesError(Exception):
    pass


@contextmanager
def cd(dirname):
    original = os.getcwd()
    os.chdir(dirname)
    try:
        yield
    finally:
        os.chdir(original)


def run(cmd):
    sys.stdout.write("Running cmd: %s\n" % cmd)
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE)
    stdout, stderr = p.communicate()
    if p.returncode != 0:
        raise BadRCError("Bad rc (%s) for cmd '%s': %s" % (
            p.returncode, cmd, stdout + stderr))
    return stdout


def bin_path():
    """
    Get the system's binary path, either `bin` on reasonable
    systems or `Scripts` on Windows.
    """
    path = 'bin'

    if platform.system() == 'Windows':
        path = 'Scripts'

    return path


def create_install_structure(working_dir, install_dir):
    if not os.path.isdir(install_dir):
        os.makedirs(install_dir)
    _create_virtualenv(location=install_dir, working_dir=working_dir)


def _create_virtualenv(location, working_dir):
    # working_dir is used (generally somewhere in /tmp) so that we
    # don't modify the install/packages directories.
    with cd(PACKAGES_DIR):
        venv = [p for p in os.listdir('.') if p.startswith('virtualenv')][0]
        compressed = tarfile.open(venv)
        compressed.extractall(path=working_dir)
        compressed.close()
    with cd(working_dir):
        # We know that virtualenv is the only dir in this directory
        # so we can listdir()[0] it.
        with cd(os.listdir('.')[0]):
            run(('%s virtualenv.py --no-download '
                 '--python %s %s') % (sys.executable,
                                      sys.executable,
                                      location))


def create_working_dir():
    d = tempfile.mkdtemp()
    return d


def pip_install_packages(install_dir):
    cli_tarball = [p for p in os.listdir(PACKAGES_DIR)
                   if p.startswith('awscli')]
    if len(cli_tarball) != 1:
        message = (
            "Multiple versions of the CLI were found in %s. Please clear "
            "out this directory before proceeding."
        )
        raise MultipleBundlesError(message % PACKAGES_DIR)
    cli_tarball = cli_tarball[0]
    pip_script = os.path.join(install_dir, bin_path(), 'pip')

    # Some packages declare `setup_requires`, which is a list of dependencies
    # to be used at setup time. These need to be installed before anything
    # else, and pip doesn't manage them.
    setup_requires_dir = os.path.join(PACKAGES_DIR, 'setup')
    with cd(setup_requires_dir):
        for package in os.listdir(setup_requires_dir):
            run('%s install --no-cache-dir --no-index --find-links file://%s %s' % (
                pip_script, setup_requires_dir, package
            ))

    with cd(PACKAGES_DIR):
        run('%s install --no-cache-dir --no-index --find-links file://%s %s' % (
            pip_script, PACKAGES_DIR, cli_tarball))


def create_symlink(real_location, symlink_name):
    if os.path.isfile(symlink_name):
        print("Symlink already exists: %s" % symlink_name)
        print("Removing symlink.")
        os.remove(symlink_name)
    symlink_dir_name = os.path.dirname(symlink_name)
    if not os.path.isdir(symlink_dir_name):
        os.makedirs(symlink_dir_name)
    os.symlink(real_location, symlink_name)
    return True


def main():
    parser = optparse.OptionParser()
    parser.add_option('-i', '--install-dir', help="The location to install "
                      "the AWS CLI.  The default value is ~/.local/lib/aws",
                      default=INSTALL_DIR)
    parser.add_option('-b', '--bin-location', help="If this argument is "
                      "provided, then a symlink will be created at this "
                      "location that points to the aws executable. "
                      "This argument is useful if you want to put the aws "
                      "executable somewhere already on your path, e.g. "
                      "-b /usr/local/bin/aws.  This is an optional argument. "
                      "If you do not provide this argument you will have to "
                      "add INSTALL_DIR/bin to your PATH.")
    opts = parser.parse_args()[0]
    working_dir = create_working_dir()
    try:
        create_install_structure(working_dir, opts.install_dir)
        pip_install_packages(opts.install_dir)
        real_location = os.path.join(opts.install_dir, bin_path(), 'aws')
        if opts.bin_location and create_symlink(real_location, opts.bin_location):
            print("You can now run: %s --version" % opts.bin_location)
        else:
            print("You can now run: %s --version" % real_location)
    finally:
        shutil.rmtree(working_dir)


if __name__ == '__main__':
    main()
